package pl.model.service.impl;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.analysis.StopAnalyzer;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.RangeQuery;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.BooleanClause.Occur;
import org.hibernate.Criteria;
import org.hibernate.FetchMode;
import org.hibernate.Session;
import org.hibernate.ejb.HibernateEntityManager;
import org.hibernate.search.bridge.StringBridge;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.FullTextQuery;
import org.hibernate.search.jpa.Search;

import pl.common.BookException;
import pl.common.PaddedIntegerBridge;
import pl.model.dao.AuthorDao;
import pl.model.dao.BookDao;
import pl.model.dao.CommentDao;
import pl.model.dao.PublisherDao;
import pl.model.dao.UserDao;
import pl.model.domain.Author;
import pl.model.domain.Book;
import pl.model.service.SearchService;

public class SearchServiceImpl implements SearchService {

	protected BookDao bookDao;
	protected AuthorDao authorDao;
	protected CommentDao commentDao;
	protected PublisherDao publisherDao;
	protected UserDao userDao;

	protected EntityManagerFactory emf;
	protected FullTextEntityManager fem;
	protected EntityManager em;

	protected final Log log = LogFactory.getLog(getClass());
	protected StringBridge rankBridge = (StringBridge) new PaddedIntegerBridge();

	/*
	 * (non-Javadoc)
	 * 
	 * @see pl.model.service.SearchService#searchBookByTitle(java.lang.String)
	 */
	public List<Book> searchBookByTitle(String title) {
		QueryParser parser = new QueryParser("title", new StopAnalyzer());
		Query query = null;
		try {
			query = parser.parse(title);
		} catch (ParseException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
		return fem.createFullTextQuery(query, Book.class).getResultList();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * pl.model.service.SearchService#searchBookByDescription(java.lang.String)
	 */
	public List<Book> searchBookByDescription(String[] keywords) {
		BooleanQuery big_query = new BooleanQuery();
		for (String keyword : keywords) {
			Query query = new TermQuery(new Term("description", keyword
					.toLowerCase()));
			big_query.add(query, Occur.SHOULD);
		}
		return fem.createFullTextQuery(big_query, Book.class).getResultList();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see pl.model.service.SearchService#searchBookByTags(java.lang.String[])
	 */
	public List<Book> searchBookByTags(String[] tags) {
		log.info("searchBookByTags " + StringUtils.join(tags, " "));
		BooleanQuery big_query = new BooleanQuery();
		
		for (String tag : tags) {
			Query query = new TermQuery(new Term("tags", tag
					.toLowerCase()));
			big_query.add(query, Occur.SHOULD);
		}
		List<Book> books = null;
		try {
		//	Criteria crit = FetchMode.LAZY;
		//	crit.setFet
			
			HibernateEntityManager hem = (HibernateEntityManager) em;
			Session session = hem.getSession();
		
			Criteria criteria = session.createCriteria( Book.class ).setFetchMode( "author", FetchMode.LAZY );

			books = fem.createFullTextQuery(big_query, Book.class).setCriteriaQuery(criteria).getResultList();
			log.info("size " + books.size());
			log.info("books " + books);
		} catch (RuntimeException e) {
			log.error(" fem: " + fem + " " + e.getMessage());
			e.printStackTrace();
		}
		for (Book book:books) book.setAuthor(null);
		return books;
		//return fem.createFullTextQuery(big_query, Book.class).getResultList();
	}

//	@Override
//	public List<Book> searchBooksByTagsTitleDescription(String keywords) {
//		List<Book> books = new LinkedList<Book>();
//
//		return books;
//	}

	@Override
	public List<Book> searchBooksByTagsTitleDescription(String[] keywords) {
		BooleanQuery big_query = new BooleanQuery();
		log.info("keywords: " + keywords);
		log.info("searchBooksByTagsTitleDescription:keywords:" + StringUtils.join(keywords, " "));
		for (String keyword : keywords) {
			BooleanQuery or_query = new BooleanQuery();
			keyword=keyword.toLowerCase();
			or_query.add(new TermQuery(new Term("description", keyword)), Occur.SHOULD);
			or_query.add(new TermQuery(new Term("tags", keyword)), Occur.SHOULD);
			or_query.add(new TermQuery(new Term("title", keyword)),Occur.SHOULD);
			big_query.add(or_query, Occur.MUST);
		}
		List<Book> books = null;
		try {
			books = fem.createFullTextQuery(big_query, Book.class).getResultList();
		} catch (RuntimeException e) {
			log.error(" fem: " + fem + " " + e.getMessage());
			e.printStackTrace();
		}
		return books;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see pl.model.service.SearchService#searchBookById(java.lang.Integer)
	 */
	public Book searchBookById(Integer id) {
		Query query = new TermQuery(new Term("id", id + ""));
		return (Book) fem.createFullTextQuery(query, Book.class)
				.getSingleResult();
	}

	public Book searchBookByIdWithProjection(Integer id) {
		Query query = new TermQuery(new Term("id", id + ""));
		Object[] tab = (Object[]) fem.createFullTextQuery(query, Book.class)
				.setProjection("id", "title", "author.id", "author.name")
				.getSingleResult();

		Book book = new Book();
		book.setId((Integer) tab[0]);
		book.setTitle((String) tab[1]);
		Integer a_id = (Integer) tab[2];
		String a_name = (String) tab[3];

		if (a_id != null) {
			Author a = new Author();
			a.setId(a_id);
			a.setName(a_name);
			book.setAuthor(a);
		}

		return book;
	}

	@Override
	public List<Book> searchBooksByRank(Integer min, Integer max) {
		Query query = new RangeQuery(new Term("rank", rankBridge
				.objectToString(min)), new Term("rank", rankBridge
				.objectToString(max)), true);
		return fem.createFullTextQuery(query, Book.class).getResultList();
	}

	public SearchResult searchBookUniversal(String search_type,
			String[] search_by, String keywords, String rank_value, 
			String sort_by, Integer page_size, Integer page_no) {
		log.info("searchBookUniversal");
		if (keywords.length() == 0 && rank_value.length() == 0) throw new BookException("Enter keywords");
//		keywords = keywords.toLowerCase();
		if (UNIVERSAL_SEARCH.equals(search_type)) {
			return universalSearch(keywords, sort_by, page_size, page_no);
		} else {
			if (search_by.length == 0) throw new BookException("Enter search criteria");
			return advancedSearch(search_by, keywords, rank_value, sort_by, page_size, page_no);
		}
	}

	public SearchResult advancedSearch(String[] search_by, 
		String keywords, String rank_value, String sort_by, 
		Integer page_size, Integer page_no) {
		
		BooleanQuery big_query = new BooleanQuery();
		String [] keyword_tab = keywords.split(" ");
		log.info("advancedSearch");
		for (String keyword : keyword_tab) {
			BooleanQuery or_query = new BooleanQuery();
			
			for (String field:search_by){
				or_query.add(new TermQuery(new Term(field, keyword)), Occur.SHOULD);
			}
			big_query.add(or_query, Occur.MUST);
		}
		if (rank_value != null && rank_value.length()>0){
			rank_value = rank_value.trim();
			String [] int_tab = rank_value.split("[: -]+|[.]{2,}");
			String min = rankBridge.objectToString(new Integer(int_tab[0]));
			String max = rankBridge.objectToString(new Integer(int_tab[1]));
			log.info("range tab size " + int_tab.length + StringUtils.join(int_tab, "$"));
			RangeQuery rq = new RangeQuery(new Term("rank", min), new Term("rank", max), true);
			big_query.add(rq, Occur.MUST);
		}
		
		return processQuery(big_query, sort_by, page_size, page_no);
	}

	public SearchResult universalSearch(String keywords, String sort_by,
		Integer page_size, Integer page_no) {
		
		BooleanQuery big_query = new BooleanQuery();
		String [] keyword_tab = keywords.split(" ");
		log.info("universalSearch");
		for (String keyword : keyword_tab) {
			BooleanQuery or_query = new BooleanQuery();
			
			or_query.add(new TermQuery(new Term("description", keyword)), Occur.SHOULD);
			or_query.add(new TermQuery(new Term("tags", keyword)), Occur.SHOULD);
			or_query.add(new TermQuery(new Term("title", keyword)), Occur.SHOULD);
			or_query.add(new TermQuery(new Term("author.name", keyword)), Occur.SHOULD);
			or_query.add(new TermQuery(new Term("author.surname", keyword)), Occur.SHOULD);

			big_query.add(or_query, Occur.MUST);
		}
		
		return processQuery(big_query, sort_by, page_size, page_no);
	}
	
	private SearchResult processQuery(Query query, String sort_by, Integer page_size, Integer page_no){
		
		SearchResult r = new SearchResult();
		FullTextQuery ftq =fem.createFullTextQuery(query, Book.class);
		
//		if (page_no == 1) 
			r.size = ftq.getResultSize();
		
		ftq.setFirstResult((page_no-1)*page_size).setMaxResults(page_size);
		
		if (sort_by != null && !sort_by.equals("relevance")) {
			Sort sort = new Sort(sort_by);
			ftq.setSort(sort);
		}
		
		r.books = ftq.getResultList();
		
		return r;
	}

	public void setBookDao(BookDao bookDao) {
		this.bookDao = bookDao;
	}

	public void setAuthorDao(AuthorDao authorDao) {
		this.authorDao = authorDao;
	}

	public void setCommentDao(CommentDao commentDao) {
		this.commentDao = commentDao;
	}

	public void setPublisherDao(PublisherDao publisherDao) {
		this.publisherDao = publisherDao;
	}

	public void setUserDao(UserDao userDao) {
		this.userDao = userDao;
	}

	@Override
	public void testDao() {
		log.debug(this.getClass().getName() + ".testDao===================");
		log.debug(this.bookDao + " " + this.authorDao);
		if (bookDao == null || authorDao == null || commentDao == null
				|| publisherDao == null || userDao == null) {
			throw new RuntimeException(
					"Spring initialization failed? no autowire?/injection?");
		}
	}

	public void setEmf(EntityManagerFactory emf) {
		this.emf = emf;
		this.em = emf.createEntityManager();
		this.fem = Search
				.createFullTextEntityManager(em);
	}
	
	public static class SearchResult {
		public Integer size = null;
		public List<Book> books;
	}

	

}
